package com.wangjiacai.hqlQuery.role.security.aspect;

import com.alibaba.fastjson.JSONObject;
import com.wangjiacai.hqlQuery.role.security.authentication.TokenAuthentication;
import com.wangjiacai.hqlQuery.role.service.TokenUserService;
import com.wangjiacai.utils.enums.ResultEnum;
import com.wangjiacai.utils.utils.JoinPointUtil;
import com.wangjiacai.utils.utils.ResultVOUtil;
import org.apache.catalina.connector.RequestFacade;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;


/*
 * @auther: wangjiacai
 *
 * @create: 2019-01-18 14:00:55
 */
@Aspect
@Component
@ConditionalOnBean(TokenUserService.class)
public class AuthenticationAspect {

    @Autowired
    private TokenUserService tokenUserService;

    @Pointcut("@within(javax.annotation.security.RolesAllowed)" +
            "|| @annotation(javax.annotation.security.RolesAllowed)")
    public void credentialValidPointcut() {
    }

    @Around(value = "credentialValidPointcut()")
    public Object credentialValid(ProceedingJoinPoint joinPoint) throws Throwable {
        Class declaringClass = JoinPointUtil.getJoinPointClass(joinPoint);
        RolesAllowed rolesAllowed = (RolesAllowed) declaringClass.getAnnotation(RolesAllowed.class);
        boolean hasRole=validAuthority(rolesAllowed);
        if(!hasRole) return null;

        Object[] params = joinPoint.getArgs();
        Class[] parameterType = getParameterType(params);
        Method targetMethod = JoinPointUtil.getJoinPointMethod(joinPoint, parameterType);
        rolesAllowed = targetMethod.getAnnotation(RolesAllowed.class);
        hasRole=validAuthority(rolesAllowed);
        if(hasRole) return joinPoint.proceed();
        return null;
    }

    private boolean validAuthority(RolesAllowed rolesAllowed){
        if(rolesAllowed==null) return true;
        String token=getToken();
        if(token==null){
            prevent();
            return false;
        }
        TokenAuthentication tokenAuthentication=tokenUserService.findUserCredentials(token, TokenAuthentication.class);
        if(tokenAuthentication==null){
            prevent();
            return false;
        }
        boolean hasRole=false;
        for(String role:rolesAllowed.value()){
            if(tokenAuthentication.getGrantedAuthority().contains(role)){
                hasRole=true;
            }
        }
        if(!hasRole){
            prevent();
            return false;
        }
        return true;
    }

    private String getToken() {
        HttpServletRequest request = (((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
        return request.getHeader("token");
    }

    private void prevent(){
        HttpServletResponse response = (((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse());
        response.setContentType("application/json;charset=UTF-8");
        try {
            response.getWriter().write(JSONObject.toJSONString(ResultVOUtil.error(ResultEnum.UNAUTHORIZED)));
        } catch (IOException e) {
            e.printStackTrace();
        }
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
    }

    private Class[] getParameterType(Object[] params) {
        Class[] parameterType = new Class[params.length];
        for (int i = 0; i < params.length; i++) {
            if (params[i].getClass() == BeanPropertyBindingResult.class) {
                parameterType[i] = BindingResult.class;
            } else if (params[i].getClass() == RequestFacade.class) {
                parameterType[i] = HttpServletRequest.class;
            } else {
                parameterType[i] = params[i].getClass();
            }
        }
        return parameterType;
    }
}
